package kr.pe.johnkim.pushchat.mobile.util;

import com.google.android.gcm.server.Constants;
import com.google.android.gcm.server.Message;
import com.google.android.gcm.server.Result;
import com.google.android.gcm.server.Sender;
import javapns.back.PushNotificationManager;
import javapns.back.SSLConnectionHelper;
import javapns.data.Device;
import javapns.data.PayLoad;
import javapns.data.PayLoadCustomAlert;
import javapns.exceptions.DuplicateDeviceException;
import javapns.exceptions.NullDeviceTokenException;
import javapns.exceptions.NullIdException;
import javapns.exceptions.UnknownDeviceException;
import kr.pe.johnkim.pushchat.mobile.domain.PushChatSMS;
import org.apache.commons.lang.math.JVMRandom;
import org.json.JSONException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.encoding.ShaPasswordEncoder;
import org.springframework.stereotype.Component;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.security.*;
import java.security.cert.CertificateException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Random;

/**
 * @author : John_Kim <cale9797@gmail.com>
 * @date : 13. 6. 12.
 */
@Component
public class PushChatCommon {

    // SLF4J Logging
    private Logger logger = LoggerFactory.getLogger(PushChatCommon.class);

    public static JVMRandom random = new JVMRandom();

    ShaPasswordEncoder encoder = new ShaPasswordEncoder();

    // Google GCM API Key.
    private @Value("${google.gcm.api.key}") String googleGCMAPIKey;

    // APNS 개발 서버 주소.
    private @Value("${apple.apns.development.server.url}") String apnsDevelopmentServerUrl;

    // APNS 운영 서버 주소.
    private @Value("${apple.apns.production.server.url}") String apnsProductionServerUrl;

    // APNS 개발 서버 포트.
    private @Value("${apple.apns.development.server.port}") String apnsDevelopmentServerPort;

    // APNS 운영 서버 포트.
    private @Value("${apple.apns.production.server.port}") String apnsProductionServerPort;

    // APNS 개발 인증 파일 위치.
    private @Value("${apple.apns.certification.development.filepath}") String apnsDevelopmentCertificationFilePath;

    // APNS 운영 인증 파일 위치.
    private @Value("${apple.apns.certification.distribution.filepath}") String apnsDistributionCertificationFilePath;

    // APNS 개발 인증 파일 암호.
    private @Value("${apple.apns.certification.development.password}") String apnsDevelopmentCertificationPassword;

    // ANPS 운영 인증 파일 암호.
    private @Value("${apple.apns.certification.distribution.password}") String apnsDistributionCertificationPassword;

    // AES-256 Secret Key.
    private @Value("${aes256.secret.key}") String aes256SecretKey;

    // AES-128 Secret Key.
    private @Value("${aes128.secret.key}") String aes128SecretKey;

    // Cafe24 SMS API. 캐릭터셋.
    private @Value("${cafe24.sms.api.charset.type}") String cafe24SmsApiCharsetType;

    // Cafe24 SMS API. URL.
    private @Value("${cafe24.sms.api.sms.url}") String cafe24SmsApiSmsUrl;

    // Cafe24 SMS API. 사용자 id.
    private @Value("${cafe24.sms.api.user.id}") String cafe24SmsApiUserId;

    // Cafe24 SMS API. 비밀번호.
    private @Value("${cafe24.sms.api.secure}") String cafe24SmsApiSecure;

    // Cafe24 SMS API. 모드.
    private @Value("${cafe24.sms.api.mode}") String cafe24SmsApiMode;

    // Cafe24 SMS API. 리턴 URL.
    private @Value("${cafe24.sms.api.return.url}") String cafe24SmsApiReturnUrl;

    @Autowired
    private AES256Cipher aes256Cipher;

    @Autowired
    private AES128Cipher aes128Cipher;

    /**
     * 인증 번호를 넘겨 준다.
     *
     * 고유한 UUID 를 생성한 뒤에 앞 4자리를 잘라서 넘겨 준다.
     *
     * @return
     */
    public String getCertificationNumber() {
        logger.info("getCertificationNumber");
        String uuid = String.valueOf(random.nextInt(999999999));

        return uuid.substring(0, 4);
    }

    /**
     * 입력 받은 패스워드를 SHA 를 이용 해서 암호화 한 뒤 반환 한다.
     *
     * @param password
     * @return
     */
    public String getEncodedPassword(String password) {
        logger.info("getEncodedPassword");
        return encoder.encodePassword(password, null);
    }

    /**
     * 현재 날짜와 시간을 2010-10-22 15:20:46 포멧의 string 으로 반환 한다.
     *
     * @return
     */
    public String getCurrentDate() {
        logger.info("getCurrentDate");

        String currentDate = "";

        Date date = new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        currentDate = simpleDateFormat.format(date);

        return currentDate;
    }

    /**
     * 사용자의 Mobile Device 로 Google GCM 을 발송 한다.
     * GCM 은 development, production 의 개념이 없기때문에 테스트용, 운영용 공통으로 사용한다.
     *
     * @param registrationId
     * @param messageContent
     * @throws java.io.IOException
     */
    public void sendASMessage(String registrationId, String messageContent) throws IOException {
        logger.info("sendASMessage");

        logger.info("API Key          : {}", this.googleGCMAPIKey);
        logger.info("Registration Id  : {}", registrationId);
        logger.info("Message Contents : {}", messageContent);

        Sender sender = new Sender(this.googleGCMAPIKey);
        Message message = new Message.Builder().addData("MESSAGE", messageContent).build();
        Result result = sender.send(message, registrationId, 2);

        if(result.getMessageId() != null) {
            String canonicalRegId = result.getCanonicalRegistrationId();
            if(canonicalRegId != null) {
                //
                logger.info("canonicalRegId   : {}", canonicalRegId);
            }
        } else {
            String error = result.getErrorCodeName();

            logger.info("error            : {}", error);

            if(error.equals(Constants.ERROR_NOT_REGISTERED)) {
                //
            }
        }
    }

    /**
     * 사용자의 Mobile Device 로 APNS 메시지를 발송 한다.
     *
     * Used for iOS Device.
     *
     * @param registrationId
     * @param messageContent
     * @throws Exception
     * @throws org.json.JSONException
     * @throws javapns.exceptions.DuplicateDeviceException
     * @throws javapns.exceptions.NullDeviceTokenException
     * @throws javapns.exceptions.NullIdException
     * @throws java.security.NoSuchProviderException
     * @throws java.security.NoSuchAlgorithmException
     * @throws java.security.KeyManagementException
     * @throws java.security.KeyStoreException
     * @throws java.security.cert.CertificateException
     * @throws java.security.UnrecoverableKeyException
     * @throws javapns.exceptions.UnknownDeviceException
     */
    public void sendIPMessage(String registrationId, String messageContent) throws Exception,
            JSONException, DuplicateDeviceException, NullDeviceTokenException, NullIdException,
            NoSuchProviderException, NoSuchAlgorithmException, KeyManagementException, KeyStoreException,
            CertificateException, UnrecoverableKeyException, UnknownDeviceException {
        logger.info("sendIPMessage");

        PayLoad complexPayLoad = new PayLoad();
        PayLoadCustomAlert payLoadCustomAlert = new PayLoadCustomAlert();

        payLoadCustomAlert.addLocKey(messageContent);
        complexPayLoad.addCustomAlert(payLoadCustomAlert);
        complexPayLoad.addBadge(1);
        complexPayLoad.addSound("default");

        logger.info("payLoad : {}", complexPayLoad);

        PushNotificationManager pushNotificationManager = PushNotificationManager.getInstance();
        pushNotificationManager.addDevice("iPhone", registrationId);

        /**
         * Apple Store 에 배포 후  Development 정보를 Production 로 수정 한다.
         * */

        logger.info("Server Url             : {}", this.apnsProductionServerUrl);
        logger.info("Server Port            : {}", this.apnsProductionServerPort);
//        logger.info("Certification FilePath : {}", this.getApnsProductionCertificationFilePath());
//        logger.info("Certification Password : {}", this.getApnsProductionCertificationPassword());

        pushNotificationManager.initializeConnection(
                this.apnsProductionServerUrl,
                Integer.parseInt(this.apnsProductionServerPort),
                this.apnsDistributionCertificationFilePath,
                this.apnsDistributionCertificationPassword,
                SSLConnectionHelper.KEYSTORE_TYPE_PKCS12
        );

        // push 를 날린다.
        Device client = pushNotificationManager.getDevice("iPhone");
        pushNotificationManager.sendNotification(client, complexPayLoad);
        pushNotificationManager.stopConnection();

        pushNotificationManager.removeDevice("iPhone");
    }

    /**
     * 테스트용.
     *
     * 사용자의 Mobile Device 로 APNS 메시지를 발송 한다.
     *
     * iPhone.
     *
     * @param registrationId
     * @param messageContent
     * @throws Exception
     * @throws JSONException
     * @throws DuplicateDeviceException
     * @throws NullDeviceTokenException
     * @throws NullIdException
     * @throws NoSuchProviderException
     * @throws NoSuchAlgorithmException
     * @throws KeyManagementException
     * @throws KeyStoreException
     * @throws CertificateException
     * @throws UnrecoverableKeyException
     * @throws UnknownDeviceException
     */
    public void testSendIPMessage(String registrationId, String messageContent, String idChat, String owner) throws Exception,
            JSONException, DuplicateDeviceException, NullDeviceTokenException, NullIdException,
            NoSuchProviderException, NoSuchAlgorithmException, KeyManagementException, KeyStoreException,
            CertificateException, UnrecoverableKeyException, UnknownDeviceException {
        logger.info("testSendIPMessage");

        PayLoad complexPayLoad = new PayLoad();
        PayLoadCustomAlert payLoadCustomAlert = new PayLoadCustomAlert();

        payLoadCustomAlert.addLocKey(messageContent);
        complexPayLoad.addCustomAlert(payLoadCustomAlert);
        complexPayLoad.addBadge(1);
        complexPayLoad.addSound("default");
        complexPayLoad.addCustomDictionary("ID_CHAT", idChat);
        complexPayLoad.addCustomDictionary("OWNER", owner);

        logger.info("payLoad : {}", complexPayLoad);

        PushNotificationManager pushNotificationManager = PushNotificationManager.getInstance();
        pushNotificationManager.addDevice("iPhone", registrationId);

        /**
         * Apple Store 에 배포 후  Development 정보를 Production 로 수정 한다.
         * */

        logger.info("Server Url     : {}", this.apnsDevelopmentServerUrl);
        logger.info("Server Port    : {}", this.apnsDevelopmentServerPort);
        logger.info("registrationId : {}", registrationId);
        logger.info("messageContent : {}", messageContent);
//        logger.info("Certification FilePath : {}", this.getApnsDevelopmentCertificationFilePath());
//        logger.info("Certification Password : {}", this.getApnsDevelopmentCertificationPassword());

        pushNotificationManager.initializeConnection(
                this.apnsDevelopmentServerUrl,
                Integer.parseInt(this.apnsDevelopmentServerPort),
                this.apnsDevelopmentCertificationFilePath,
                this.apnsDevelopmentCertificationPassword,
                SSLConnectionHelper.KEYSTORE_TYPE_PKCS12
        );

        // push 를 날린다.
        Device client = pushNotificationManager.getDevice("iPhone");
//        pushNotificationManager.sendNotification(client, payLoad);
        pushNotificationManager.sendNotification(client, complexPayLoad);
        pushNotificationManager.stopConnection();

        pushNotificationManager.removeDevice("iPhone");
    }

    /**
     * '-' 없이 연결된 전화번호를 '-' 를 사용해서 구별해서 반환한다.
     *
     * @param phoneNumber
     * @return
     */
    public String getSMSPhoneNumber(String phoneNumber) {

        String result = "";
        int phoneNumberLength = phoneNumber.length();

        if(phoneNumberLength == 10) {
            // 3자리 국번호가 '-' 가 없을때.
            String temp1 = phoneNumber.substring(0, 3);
            String temp2 = phoneNumber.substring(3, 6);
            String temp3 = phoneNumber.substring(6, 10);
            result = temp1 + "-" + temp2 + "-" + temp3;
        } else if(phoneNumberLength == 11) {
            // 4자리 국번호가 '-' 가 없을때.
            String temp1 = phoneNumber.substring(0, 3);
            String temp2 = phoneNumber.substring(3, 7);
            String temp3 = phoneNumber.substring(7, 11);
            result = temp1 + "-" + temp2 + "-" + temp3;
        } else if(phoneNumberLength == 12) {
            // 3자리 국번호가 '-' 가 있을때.
        } else if(phoneNumberLength == 13) {
            // 4자리 국번호가 '-' 가 있을때.
        }

        return result;
    }

    /**
     * Null check.
     *
     * 인증번호 전송할때 사용한다.
     *
     * @param str
     * @param Defaultvalue
     * @return
     * @throws Exception
     */
    public static String nullcheck(String str,  String Defaultvalue ) throws Exception {

        String ReturnDefault = "" ;

        if (str == null) {
            ReturnDefault =  Defaultvalue ;
        } else if (str == "" ) {
            ReturnDefault =  Defaultvalue ;
        } else {
            ReturnDefault = str ;
        }

        return ReturnDefault ;
    }

    /**
     * Base64 Encoder.
     *
     * 인증번호 전송할때 사용한다.
     *
     * @param str
     * @return
     * @throws java.io.IOException
     */
    public static String base64Encode(String str)  throws java.io.IOException {
        sun.misc.BASE64Encoder encoder = new sun.misc.BASE64Encoder();
        byte[] strByte = str.getBytes();
        String result = encoder.encode(strByte);

        return result ;

    }

    /**
     * Base64 Decoder.
     *
     * 인증번호 전송할때 사용한다.
     *
     * @param str
     * @return
     * @throws java.io.IOException
     */
    public static String base64Decode(String str)  throws java.io.IOException {
        sun.misc.BASE64Decoder decoder = new sun.misc.BASE64Decoder();
        byte[] strByte = decoder.decodeBuffer(str);
        String result = new String(strByte);

        return result ;

    }

    /**
     * cafe24 API 를 사용해서 SMS 를 발송한다.
     *
     * 1. sms_url(전송요청 url) : http://sslsms.cafe24.com/sms_sender.php
     * 2. user_id(아이디)      : better3801
     * 3. secure(인증키)       : eb174f728a17edbd56447225c8754fb7
     *
     * @param pushChatSMS
     * @return
     * @throws Exception
     */
    public PushChatSMS sendSMS(PushChatSMS pushChatSMS) throws Exception {
        logger.info("sendSMS");

        String charsetType = cafe24SmsApiCharsetType; //EUC-KR 또는 UTF-8

        String sms_url = cafe24SmsApiSmsUrl; // SMS 전송요청 URL

        String user_id = base64Encode(cafe24SmsApiUserId); // SMS아이디
        String secure = base64Encode(cafe24SmsApiSecure);//인증키
        String msg = base64Encode(pushChatSMS.getMsg());
        String rphone = base64Encode(pushChatSMS.getRphone());
        String sphone1 = base64Encode(pushChatSMS.getSphone1());
        String sphone2 = base64Encode(pushChatSMS.getSphone2());
        String sphone3 = base64Encode(pushChatSMS.getSphone3());
        String rdate = base64Encode(pushChatSMS.getRdate());
        String rtime = base64Encode(pushChatSMS.getRtime());
        String mode = base64Encode(cafe24SmsApiMode);
        String testflag = base64Encode(pushChatSMS.getTestflag());
        String destination = base64Encode(pushChatSMS.getDestination());
        String repeatFlag = base64Encode(pushChatSMS.getRepeatFlag());
        String repeatNum = base64Encode(pushChatSMS.getRepeatNum());
        String repeatTime = base64Encode(pushChatSMS.getRepeatTime());
        String returnurl = cafe24SmsApiReturnUrl;
        String nointeractive = pushChatSMS.getNointeractive();

        String[] host_info = sms_url.split("/");
        String host = host_info[2];
        String path = "/" + host_info[3];
        int port = 80;

        String arrKey[]
                = new String[] {"user_id","secure","msg", "rphone","sphone1","sphone2","sphone3","rdate","rtime"
                ,"mode","testflag","destination","repeatFlag","repeatNum", "repeatTime"};
        String valKey[]= new String[arrKey.length] ;
        valKey[0] = user_id;
        valKey[1] = secure;
        valKey[2] = msg;
        valKey[3] = rphone;
        valKey[4] = sphone1;
        valKey[5] = sphone2;
        valKey[6] = sphone3;
        valKey[7] = rdate;
        valKey[8] = rtime;
        valKey[9] = mode;
        valKey[10] = testflag;
        valKey[11] = destination;
        valKey[12] = repeatFlag;
        valKey[13] = repeatNum;
        valKey[14] = repeatTime;

        String boundary = "";
        Random rnd = new Random();
        String rndKey = Integer.toString(rnd.nextInt(32000));
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] bytData = rndKey.getBytes();
        md.update(bytData);
        byte[] digest = md.digest();

        for(int i =0;i<digest.length;i++) {
            boundary = boundary + Integer.toHexString(digest[i] & 0xFF);
        }

        boundary = "---------------------"+boundary.substring(0,10);

        // 본문 생성
        String data = "";
        String index = "";
        String value = "";

        for (int i=0;i<arrKey.length; i++) {
            index =  arrKey[i];
            value = valKey[i];
            data +="--"+boundary+"\r\n";
            data += "Content-Disposition: form-data; name=\""+index+"\"\r\n";
            data += "\r\n"+value+"\r\n";
            data +="--"+boundary+"\r\n";
        }

        //out.println(data);

        InetAddress addr = InetAddress.getByName(host);
        Socket socket = new Socket(host, port);

        // 헤더 전송
        BufferedWriter wr = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), charsetType));
        wr.write("POST "+path+" HTTP/1.0\r\n");
        wr.write("Content-Length: "+data.length()+"\r\n");
        wr.write("Content-type: multipart/form-data, boundary="+boundary+"\r\n");
        wr.write("\r\n");

        // 데이터 전송
        wr.write(data);
        wr.flush();

        // 결과값 얻기
        BufferedReader rd = new BufferedReader(new InputStreamReader(socket.getInputStream(),charsetType));
        String line;
        String alert = "";
        ArrayList tmpArr = new ArrayList();

        while ((line = rd.readLine()) != null) {
            tmpArr.add(line);
        }

        wr.close();
        rd.close();

        String tmpMsg = (String)tmpArr.get(tmpArr.size()-1);
        String[] rMsg = tmpMsg.split(",");
        String Result= rMsg[0]; //발송결과
        String Count= ""; //잔여건수

        if(rMsg.length>1) {
            Count= rMsg[1];
        }

        // 발송결과 알림.

        PushChatSMS result = new PushChatSMS();

        if(Result.equals("success")) {
            result.setResult("success to send SMS.");
        } else if(Result.equals("3205")) {
            result.setResult("fail. wrong phone number format.");
        }

        return result;
    }

    /**
     * AES-256 대칭키를 이용해서 문자열을 암호화해서 반환 한다.
     *
     * @param str
     * @return
     * @throws NoSuchPaddingException
     * @throws InvalidAlgorithmParameterException
     * @throws UnsupportedEncodingException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     */
    public String getAes256Encode(String str) throws NoSuchPaddingException, InvalidAlgorithmParameterException, UnsupportedEncodingException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, InvalidKeyException {

        logger.info("getAes256Encode");

        return aes256Cipher.AES_Encode(str, aes256SecretKey);
    }

    /**
     * AES-256 대칭키를 이용해서 문자열을 복호화해서 반환 한다.
     *
     * @param str
     * @return
     * @throws NoSuchPaddingException
     * @throws InvalidAlgorithmParameterException
     * @throws UnsupportedEncodingException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     */
    public String getAes256Decode(String str) throws NoSuchPaddingException, InvalidAlgorithmParameterException, UnsupportedEncodingException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, InvalidKeyException {

        logger.info("getAes256Decode");

        return aes256Cipher.AES_Decode(str, aes256SecretKey);
    }

    /**
     * AES-128 대칭키를 이용해서 문자열을 암호화해서 반환 한다.
     *
     * @param str
     * @return
     * @throws Exception
     */
    public String getAes128Encode(String str) throws Exception {
        logger.info("getAes128Encode");

        return aes128Cipher.encrypt(str, aes128SecretKey);
    }

    /**
     * AES-128 대칭키를 이용해서 문자열을 복호화해서 반환 한다.
     *
     * @param str
     * @return
     * @throws Exception
     */
    public String getAes128Decode(String str) throws Exception {
        logger.info("getAes128Decode");

        return aes128Cipher.decrypt(str, aes128SecretKey);
    }
}
